// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: donggu

#include "coin2/base/config/app_config.h"

#include <fstream>
#include <memory>
#include <string>
#include <utility>
#include <vector>

#include <google/protobuf/util/json_util.h>

#include "coin2/app/driver.pb.h"
#include "coin2/base/log.h"
#include "coin2/base/time.h"
#include "coin2/base/time.pb.h"
#include "coin2/exchange/feed.pb.h"

namespace coin2::base::config {

using coin2::app::DriverConfig;
using coin2::exchange::feed::ArchiveConfig;

std::pair<int64_t, int64_t> GetEnclosingInterval(const DriverConfig& config) {
  std::pair<int64_t, int64_t> enclosure;
  enclosure.first = 0;
  enclosure.second = 0;
  {
    const auto& sys = config.feed().common();
    if (sys.archive().has_interval()) {
      auto interval = FromIntervalConfig(sys.archive().interval());
      if (enclosure.first == 0 || interval.first < enclosure.first) {
        enclosure.first = interval.first;
      }
      if (enclosure.second == 0 || interval.second > enclosure.second) {
        enclosure.second = interval.second;
      }
    }
  }
  for (const auto& [_name, sys] : config.feed().exchanges()) {
    if (sys.archive().has_interval()) {
      auto interval = FromIntervalConfig(sys.archive().interval());
      if (enclosure.first == 0 || interval.first < enclosure.first) {
        enclosure.first = interval.first;
      }
      if (enclosure.second == 0 || interval.second > enclosure.second) {
        enclosure.second = interval.second;
      }
    }
  }
  for (const auto& [_name, sys] : config.order().exchanges()) {
    if (sys.archive().has_interval()) {
      auto interval = FromIntervalConfig(sys.archive().interval());
      if (enclosure.first == 0 || interval.first < enclosure.first) {
        enclosure.first = interval.first;
      }
      if (enclosure.second == 0 || interval.second > enclosure.second) {
        enclosure.second = interval.second;
      }
    }
  }
  return enclosure;
}

std::pair<int64_t, int64_t> FromIntervalConfig(const IntervalConfig& config) {
  if (config.has_start() + config.has_end() + config.has_duration() == 0) {
    return std::make_pair(0LL, 0LL);
  }
  CHECK_EQ_THROW(config.has_start() + config.has_end() + config.has_duration(), 2)
      << "The interval must have exactly 2 out of 3 fields";
  int64_t start = 0;
  int64_t end = 0;
  int64_t duration = 0;
  if (config.has_start()) {
    start = TimestampFromString(config.start());
  }
  if (config.has_end()) {
    end = TimestampFromString(config.end());
  }
  if (config.has_duration()) {
    duration = DurationFromString(config.duration());
  }
  if (duration != 0) {
    if (start != 0) {
      end = start + duration;
    } else {
      start = end - duration;
    }
  }
  return std::make_pair(start, end);
}

DriverConfig DriverConfigFromJson(const std::experimental::filesystem::path& path) {
  std::ifstream in(path);
  std::string str(dynamic_cast<std::stringstream const&>(std::stringstream() << in.rdbuf()).str());

  DriverConfig app_config;
  auto status = google::protobuf::util::JsonStringToMessage(str, &app_config);
  CHECK_THROW(status.ok()) << "Fail to parse DriverConfig from " << path << "\n"
                           << status;
  return app_config;
}

void ValidateArchiveConfig(const ArchiveConfig& config) {
  CHECK_GT_THROW(config.machine().size(), 0);
  FromIntervalConfig(config.interval());
  CHECK_GT_THROW(config.max_record_size(), 0);
}
}  // namespace coin2::base::config
